package parsing;

import java.util.ArrayList;
import java.util.Stack;

import functions.Cos;
import functions.Exp;
import functions.Function;
import functions.FunctionIF;
import functions.Ln;
import functions.PolynTerm;
import functions.Sin;

/**
 * this class takes String and parses it to function
 * 
 */

public class Parser {

	public Parser() {
	}

	public Function parse(String func) throws InvalidFunctionException {
		func = func.replaceAll(" ", "");
		func = func.replaceAll("        ", "");
		func = func.toLowerCase();
		func = validate(func);

		if (!ParenthesesCheck(func))
			throw new IllegalStateException("unbalanced Parentheses");
		return myParse(func);
	}
	
	public Function myParse(String func) {
		int parenCount = 0;
		ArrayList<FunctionIF> product = new ArrayList<FunctionIF>();
		Function function = new Function();
		for (int i = 0; i < func.length(); i++) {
			/*
			 * if there is a bracket find the closing bracket for this bracket
			 * and call yourself with a substring of the current function
			 */
			if (func.charAt(i) == '(') {
				parenCount++;
				for (int j = i + 1; j < func.length(); j++) {
					if (func.charAt(j) == '(')
						parenCount++;
					else if (func.charAt(j) == ')')
						parenCount--;
					if (parenCount == 0) {
						String temp = func.substring(i + 1, j);
						Function f = myParse(temp);
						// set the exponent of the function
						double Funcexponent = 1;
						if (i > 0) {
							if (func.charAt(i - 1) == '/') {
								Funcexponent = -1;
							}
						}

						if (i > 0 && func.charAt(i - 1) == '-')
							f.setCoff(-1);

						i = j + 1;

						// check the exponent of the fucntion
						if (i < func.length() && func.charAt(i) == '^') {
							try {
								Funcexponent *= Double.parseDouble(func
										.substring(i + 1));
								j += String.valueOf(
										Double.parseDouble(func
												.substring(i + 1))).length() - 1;
								i = j;
							} catch (Exception e) {
								for (int k = i; k < func.length(); k++) {
									if (func.charAt(k) == '*'
											|| func.charAt(k) == '+'
											|| func.charAt(k) == '-'
											|| func.charAt(k) == '/') {
										Funcexponent *= Double.parseDouble(func
												.substring(i + 1, k));
										j += String.valueOf(
												Double.parseDouble(func
														.substring(i + 1, k)))
												.length() - 1;
										i = j;
										break;
									}
								}
							}
						}

						f.setExpon(Funcexponent);
						product.add(f);

						// add new term
						if (j + 1 < func.length()
								&& (func.charAt(j + 1) == '+' || func
										.charAt(j + 1) == '-')) {
							function.addProductTerm(product);
							product = new ArrayList<FunctionIF>();
						} else if (j + 1 == func.length()) {
							function.addProductTerm(product);
							product = new ArrayList<FunctionIF>();
						}

						break;
					}
				}
				// exp, ln, sin, cosine
			} else if (func.charAt(i) == 'e' || func.charAt(i) == 'l'
					|| func.charAt(i) == 's' || func.charAt(i) == 'c') {
				int parenCount1 = 0;
				FunctionIF myFunction = null;
				Function insideBracket = null;
				int exponent = 1;
				if (i > 0 && func.charAt(i - 1) == '/')
					exponent = -1;
				int coff = 1;
				if (i > 0 && func.charAt(i - 1) == '-')
					coff = -1;
				i += 3;
				for (int j = i; j < func.length(); j++) {
					if (func.charAt(j) == '(')
						parenCount1++;
					else if (func.charAt(j) == ')')
						parenCount1--;
					if (parenCount1 == 0) {
						String temp = func.substring(i + 1, j);
						insideBracket = myParse(temp);

						if (func.charAt(i - 3) == 'e') {
							myFunction = new Exp(coff, exponent, insideBracket);
						} else if (func.charAt(i - 3) == 'l') {
							myFunction = new Ln(coff, exponent, insideBracket);
						} else if (func.charAt(i - 3) == 's') {
							myFunction = new Sin(coff, exponent, insideBracket);
						} else if (func.charAt(i - 3) == 'c') {
							myFunction = new Cos(coff, exponent, insideBracket);
						}
						i = j + 1;
						break;
					}
				}

				product.add(myFunction);

				if (i < func.length() && func.charAt(i) == '^') {
					// the end of the function
					try {
						double exp = Double.parseDouble(func.substring(i + 1));
						myFunction.setExpon(myFunction.getExpon() * exp);
						function.addProductTerm(product);
						product = new ArrayList<FunctionIF>();
					} catch (Exception e) {
						for (int j = i; j < func.length(); j++) {
							if (func.charAt(j) == '+' || func.charAt(j) == '-') {
								double exp = Double.parseDouble(func.substring(
										i + 1, j));
								myFunction
										.setExpon(myFunction.getExpon() * exp);
								function.addProductTerm(product);
								product = new ArrayList<FunctionIF>();
								i = j;
								break;
							} else if (func.charAt(j) == '*'
									|| func.charAt(j) == '/') {
								double exp = Double.parseDouble(func.substring(
										i + 1, j));
								myFunction
										.setExpon(myFunction.getExpon() * exp);
								i = j;
								break;
							}
						}
					}
				} else {

					// add the function
					if (i == func.length()) {
						function.addProductTerm(product);
						product = new ArrayList<FunctionIF>();
					} else {
						// loop to find the next operation and add the function
						for (int j = i; j < func.length(); j++) {
							if (func.charAt(j) == '+' || func.charAt(j) == '-') {
								function.addProductTerm(product);
								product = new ArrayList<FunctionIF>();
								break;
							}
						}
					}
				}
			} // Base case... polynomial terms
			else if (func.charAt(i) == 'x') {
				PolynTerm p = new PolynTerm(Double.MIN_VALUE, Double.MIN_VALUE);

				// loop to find the coefficient
				int exponent = 1;
				for (int j = i - 1; j >= 0; j--) {
					// set the exponent
					if (func.charAt(j) == '/') {
						if (func.substring(j + 1, i).equals(""))
							p.setCoff(1);
						exponent = -1;
						break;
					} else if (func.charAt(j) == '+' || func.charAt(j) == '*') {
						if (func.substring(j + 1, i).equals(""))
							p.setCoff(1);
						else
							p.setCoff(Integer.parseInt(func.substring(j + 1, i)));
						break;
						// multiply the coefficient with -1
					} else if (func.charAt(j) == '-') {
						if (func.substring(j + 1, i).equals(""))
							p.setCoff(-1);
						else
							p.setCoff(-1
									* Integer.parseInt(func.substring(j + 1, i)));
						break;
					}
				}
				// then its the first term in the equation
				if (p.getCoff() == Double.MIN_VALUE) {
					if (func.subSequence(0, i).equals(""))
						p.setCoff(1);
					else
						p.setCoff(Double.parseDouble(func.substring(0, i)));
				}

				// p.setExpon(p.getExpon() * exponent);

				// loop to find the exponent
				for (int j = i + 1; j < func.length(); j++) {
					if (func.charAt(j) == '+' /* || func.charAt(j) == '-' */) {
						p.setExpon(exponent
								* Double.parseDouble(func.substring(i + 2, j)));
						i = j;
						product.add(p);
						function.addProductTerm(product);
						product = new ArrayList<FunctionIF>();
						break;
					} else if (func.charAt(j) == '/' || func.charAt(j) == '*') {
						p.setExpon(exponent
								* Double.parseDouble(func.substring(i + 2, j)));
						product.add(p);
						i = j;
						break;
						// negative power
					} else if (func.charAt(j) == '-') {
						if (func.charAt(j-1)!='^') {
							p.setExpon(exponent
									* Double.parseDouble(func.substring(i + 2, j)));
							i = j;
							product.add(p);
							function.addProductTerm(product);
							product = new ArrayList<FunctionIF>();
							break;
						} else {
							for (int j2 = j + 1; j2 < func.length(); j2++) {
								if (func.charAt(j2) == '+'
										|| func.charAt(j2) == '-') {
									p.setExpon(exponent
											* Double.parseDouble(func
													.substring(j, j2)));
									i = j2;
									j = j2;
									product.add(p);
									function.addProductTerm(product);
									product = new ArrayList<FunctionIF>();
									break;
								} else if (func.charAt(j2) == '*'
										|| func.charAt(j2) == '/') {
									p.setExpon(exponent
											* Double.parseDouble(func
													.substring(j, j2)));
									product.add(p);
									i = j2;
									j = j2;
									break;
								}
							}
						}
					}
				}

				if (p.getExpon() != Double.MIN_VALUE)
					continue;

				// then its the last term in the equation and no next term
				if (p.getExpon() <= Double.MIN_VALUE) {
					p.setExpon(exponent
							* Double.parseDouble(func.substring(i + 2)));
					product.add(p);
					// if it is the end of the function
					// or the next operation is '+' of '-'
					if (i + 1 + func.substring(i + 2).length() == func.length() - 1
							|| (i + 2 < func.length() && (func.charAt(i + 2) == '+' || func
									.charAt(i) == '-')))
						function.addProductTerm(product);
					// product = new ArrayList<FunctionIF>();
					break;
				}

				// parse constant function
			} else if (Character.isDigit(func.charAt(i))) {
				PolynTerm p = new PolynTerm(Double.MIN_VALUE, 0);
				// find the value of the constant
				int exponent = 1;
				if (i > 0 && func.charAt(i - 1) == '/') {
					exponent = -1;
				}
				for (int j = i + 1; j < func.length(); j++) {
					if (func.charAt(j) == '+' || func.charAt(j) == '-') {
						double coff = Double.parseDouble(func.substring(i, j));
						// check if it is negative or positive
						if (i > 0 && func.charAt(i - 1) == '-')
							coff = coff * -1;
						// reverse the coefficient
						// e.g 4/2 = 4 * (1/2)
						if (exponent == -1) {
							p.setCoff(1 / coff);
						} else
							p.setCoff(coff);
						product.add(p);
						i = j;
						function.addProductTerm(product);
						product = new ArrayList<FunctionIF>();
						break;
					} else if (func.charAt(j) == '/' || func.charAt(j) == '*') {
						double coff = Double.parseDouble(func.substring(i, j));
						// check if it is negative or positive
						if (i > 0 && func.charAt(i - 1) == '-')
							coff = coff * -1;
						if (exponent == -1) {
							p.setCoff(1 / coff);
						} else
							p.setCoff(coff);
						product.add(p);
						// check if it is multiplied by a bracket
						// e.g 2 * (x + 5)
						if (func.charAt(j + 1) == '(') {
							int parenCount1 = 1;
							for (int k = j + 2; k < func.length(); k++) {
								if (func.charAt(k) == '(')
									parenCount1++;
								else if (func.charAt(k) == ')')
									parenCount1--;
								if (parenCount1 == 0) {

									String temp = func.substring(j + 2, k);
									Function f = myParse(temp);
									// set the exponent of the function
									double Funcexponent = 1;
									if (j > 0) {
										if (func.charAt(j) == '/') {
											Funcexponent = -1;
										}
									}
									f.setExpon(Funcexponent);
									product.add(f);

									// add new term
									if (k + 1 < func.length()
											&& (func.charAt(k + 1) == '+' || func
													.charAt(k + 1) == '-')) {
										function.addProductTerm(product);
										product = new ArrayList<FunctionIF>();
									} else if (k + 1 == func.length()) {
										function.addProductTerm(product);
										product = new ArrayList<FunctionIF>();
									}
									j = k + 1;
									break;
								}
							}
						}
						i = j;
						break;
					} else if (func.charAt(j) == '^') {
						p.setCoff(Double.parseDouble(func.substring(i, j)));
						// double exp = Double.parseDouble(func.substring(j+1));
						// p.setExpon(exp);
						try {
							p.setCoff(Math.pow(p.getCoff(),
									Double.parseDouble(func.substring(j + 1))));
							product.add(p);
							function.addProductTerm(product);
							i = func.length();
							j = func.length();
							product = new ArrayList<FunctionIF>();
							break;
						} catch (Exception e) {
							for (int k = j + 1; k < func.length(); k++) {
								if (func.charAt(k) == '+'
										|| func.charAt(k) == '-') {
									p.setCoff(Math.pow(p.getCoff(), Double
											.parseDouble(func.substring(j + 1,
													k))));
									product.add(p);
									function.addProductTerm(product);
									product = new ArrayList<FunctionIF>();
									i = k;
									j = func.length();
									break;
								} else if (func.charAt(k) == '*'
										|| func.charAt(k) == '/') {
									p.setCoff(Math.pow(p.getCoff(), Double
											.parseDouble(func.substring(j + 1,
													k))));
									product.add(p);
									// function.addProductTerm(product);
									i = k;
									j = func.length();
								}
							}
						}
					}
				}

				// if the constant is the last term in the function
				if (p.getCoff() == Double.MIN_VALUE) {
					if (exponent == -1) {
						p.setCoff(1 / Double.parseDouble(func.substring(i)));
					} else
						p.setCoff(Double.parseDouble(func.substring(i)));
					if (i == 0) {
						product.add(p);
						function.addProductTerm(product);
					} else {
						if (func.charAt(i - 1) == '+') {
							product.add(p);
							function.addProductTerm(product);
						} else if (func.charAt(i - 1) == '*'
								|| func.charAt(i - 1) == '/') {
							product.add(p);
							function.addProductTerm(product);
						} else if (func.charAt(i - 1) == '-') {
							p.setCoff(p.getCoff() * -1);
							product.add(p);
							function.addProductTerm(product);

						}
					}
				}
			}
		}
		return function;
	}
	/** check the parentheses balance in the function */
	public boolean ParenthesesCheck(String s) {
		Stack<Character> stack = new Stack<Character>();
		for (int i = 0; i < s.length(); i++) {
			if (s.charAt(i) == '(')
				stack.push(s.charAt(i));
			else if (s.charAt(i) == ')') {
				if (stack.isEmpty()) {
					throw new IllegalStateException("unbalanced parentheses");
				} else
					stack.pop();
			}
		}
		return stack.isEmpty();
	}

	public String validate(String f) throws InvalidFunctionException{
		boolean operator = false;
		boolean minus=true;
		for(int i=0;i<f.length();i++)
		{
			char c = f.charAt(i);
			if(c=='.')
			{
				//decimal point should be between two numbers
				try{
				if(f.charAt(i-1)<'0' || f.charAt(i-1)>'9' || f.charAt(i+1)<'0' || f.charAt(i-1)>'9')
					throw new InvalidFunctionException("decimal point should be betwwen two numbers");
				}catch(StringIndexOutOfBoundsException e){
					throw new InvalidFunctionException("decimal point should be betwwen two numbers");
				}
				operator=false;
				minus=false;
			}
			else
				//numerical values
				if(c>='0' && c<='9')
				{
					if(operator)
					{
						try
						{
						if(!(f.charAt(i-1)>='0' && f.charAt(i-1)<='9') && f.charAt(i-1)!='(')
							throw new InvalidFunctionException("operator should exist between operands");
						}catch(StringIndexOutOfBoundsException e)
						{
							throw new InvalidFunctionException("operator should exist between operands");
						}
					}
					operator = true;
					minus=true;
				}
				else
					if(c=='-')
					{
						if(!minus || i==f.length()-1)
							throw new InvalidFunctionException("operator should exist between operands");
						operator=false;
						minus=false;
					}
					else
						if(c=='+' || c=='*' || c=='/' || c=='^')
						{
							if(!operator || i==f.length()-1)
								throw new InvalidFunctionException("operator should exist between operands");
							minus=true;
							operator =false;
						}
						else
							if(c=='s' || c=='c' || c=='l' || c=='e'|| c=='x')
							{
								try
								{
								if(!isOperator(f,i-1) && f.charAt(i-1) !='(')
									throw new InvalidFunctionException("operator should be between operands");
								}catch(StringIndexOutOfBoundsException e)
								{}
								switch(c)
								{
								case 'x':
								{
									try
									{
										if(f.charAt(i+1) != '^')
										{
											f=f.substring(0,i+1) + "^1" + f.substring(i+1,f.length());
											i+=2;
										}
									}catch(StringIndexOutOfBoundsException e)
									{
										f=f+"^1";
										i+=2;
									}
									operator=true;
									minus=true;
								}
								break;
								case 's':
								{
									if(!f.substring(i+1, i+4).equals("in("))
										throw new InvalidFunctionException("error in reading sine function");
									i+=3;
									operator=false;
									minus=true;
								}
								break;
								case 'c':
								{
									if(!f.substring(i+1, i+4).equals("os("))
										throw new InvalidFunctionException("error in reading cosine function");
									i+=3;
									operator=false;
									minus=true;
								}
								break;
								case 'e':
								{
									if(!f.substring(i+1, i+4).equals("xp("))
										throw new InvalidFunctionException("error in reading e function");
									i+=3;
									operator=false;
									minus=true;
								}
								break;
								case 'l':
								{
									if(!f.substring(i+1, i+4).equals("og("))
										throw new InvalidFunctionException("error in reading log function");
									i+=3;
									operator=false;
									minus=true;
								}
								break;
								}
							}
							else
								switch(c)
								{
								case '(':
								{
									minus=true;
									operator=false;  
								}
								break;
								case')':
								{
									try
									{
									if(f.charAt(i-1)=='(' || isOperator(f,i-1))
										throw new InvalidFunctionException("error near the brackets");
									}catch(StringIndexOutOfBoundsException e)
									{
										throw new InvalidFunctionException("bracket openned at the end of the line !");
									}
									minus=true;
									operator=true;
								}
								break;
								default:throw new InvalidFunctionException("invalid character : "+c);
								}
		}
		return f;
	}
	private boolean isOperator(String f, int i) {
		char c = f.charAt(i);
		if (c == '+' || c == '-' || c == '*' || c == '/' || c == '^')
			return true;
		return false;
	}

	public static void main(String[] args) throws InvalidFunctionException {
		Parser p = new Parser();
		String[] testCaes = {"sin(cos((x^2)*(x-2)/(x*x*x - x*x - 1)+99.7656-123.2*x^2))"};
		for(String s : testCaes)
			p.parse(s);
		// (x^1+5)*(2*x^1+7)
		// (x^1+1)/(x^1+2)*2+0.5
		// (12/2+1)-1*3
		// (sin(x^1))^5
		// 1 * x^1 *( 15 + 2 * ( 2 + ( 1 * x ^ 2 + 5 ) + ( 1 * x ^ 1 ) * ( 1 * x
		// ^ 1 + 2 ) + 1 ) )
		Function f = p.parse("x*sin(x) + -1");
		System.out.println(f.eval(1));
		
		f = p.parse("sin(x)^2 + cos(x)^2");
		System.out.println(f.eval(25));
		
		f = p.parse("x-2*x");
		System.out.println(f.eval(5));
		
		
	}

}